/**
 * 
 */
package engine;

import common.Dice;
import gui.customControl.LogWindow;
import data.Player;
import data.Position;
import data.Data.PlayerState;
import data.Data.Skill;

/**
 * Implements the details of the actions performed by the player.
 */
public class ActionManager 
{
	// passing modifiers
	public static final int THROW_MODIFIER_QUICK_PASS 		= 1;
	public static final int THROW_MODIFIER_SHORT_PASS 		= 0;
	public static final int THROW_MODIFIER_LONG_PASS 		= -1;
	public static final int THROW_MODIFIER_LONG_BOMB 		= -2;
	
	// catching modifiers
	public static final int CATCH_MODIFIER_ACCURATE_PASS	= 1;
	public static final int CATCH_MODIFIER_SCATTERED_BALL	= 0;
	
	// common modifiers
	public static final int MODIFIER_ENEMY_TACKLE			= -1;
	
	// picking up the ball modifier
	public static final int PICKUP_MODIFIER					= 1;
	
	// dodging modifier
	public static final int DODGE_MODIFIER                  = 1;
	
	// ruler size
	public static final int RULER_QUICK_PASS				= 3;
	public static final int RULER_SHORT_PASS				= 6;
	public static final int RULER_LONG_PASS					= 9;
	public static final int RULER_LONG_BOMB					= 12;
	
	// pass agility table
	private static final int[] AGILITY_TABLE				= {6, 5, 4, 3, 2, 1};
	
	
	
	/**
	 * Container class for holding the result of a throwing action.
	 */
	public static class ThrowBallResult
	{
		/** Specifies if the ball landed on the intended position */
		public boolean IsAccurate = false;
		/** The position where the ball landed */
		public Position BallPosition = new Position();
		/** The Thrower player  */
		public Player thrower;
		/** Specifies if the position represents the last valid position of the ball before it went off the pitch */
		public boolean IsOutsidePitch;
		
		public ThrowBallResult(Position ballPos, boolean isAccurate,Player playerThrower)
		{
			IsAccurate = isAccurate;
			BallPosition = ballPos;
			thrower=playerThrower;
			IsOutsidePitch = false;
		}
	}
	
	/**
	 * Container class for holding the result of a throwing action.
	 */
	public static class CatchBallResult
	{
		/** Specifies if the ball was successfully caught */
		public boolean Success = false;
		/** The position where the ball landed */
		public Position BallPosition = new Position(); 
		
		public CatchBallResult(Position ballPos, boolean success)
		{
			Success = success;
			BallPosition = ballPos;
		}
	}
	
	/**
	 * Throws the ball to a given location
	 * @param player - the thrower
	 * @param target - the target location
	 * @return Returns the result of the action as a ThrowBallResult
	 */
	public static ThrowBallResult throwBall(Player player, Position target)
	{
		assert( Pitch.isInsidePitch(target) );
		
		Position ballPosition = target;
		boolean isAccurate = true;
		String message = "";
	
		// Compute distance
		int distance = player.distance(target);
		int distanceModifier = 0;

		if(distance <= RULER_QUICK_PASS)
		{
			distanceModifier = THROW_MODIFIER_QUICK_PASS;
			message = "quick pass";
		}
		else if( distance <= RULER_SHORT_PASS )
		{
			distanceModifier = THROW_MODIFIER_SHORT_PASS;
			message = "short pass";
		}
		else if( distance <= RULER_LONG_PASS )
		{
			distanceModifier = THROW_MODIFIER_LONG_PASS;
			message = "long pass";
		}
		else if( distance <= RULER_LONG_BOMB )
		{
			distanceModifier = THROW_MODIFIER_LONG_BOMB;
			message = "long bomb";
		}
		else
		{
			LogWindow.instance().Log(player.name() + " cannot throw the ball that far!");
			return null;
		}
		
		// Once we have validated that the distance is ok, 
		// We can proceed to the throw roll:
		// First, the thrower no longer has the ball
		player.setHasBall(false);
		player.setHasActed(true);
		player.select(false);
		boolean playerHasReroll = player.hasSkill(Skill.PASS);
		
		boolean rollAgain = true;
		while( rollAgain )
		{
			int roll = Dice.rollDice(6);
			int modRoll = roll + GameEngine.getInstance().countEnemyTackleZones(player) * MODIFIER_ENEMY_TACKLE + distanceModifier;
			
			// Roll is an automatic fail if 1,
			//            automatic success if 6
			// Modified roll counts distance, enemy players around, and tests against the agility table
			LogWindow.instance().Log("> Throw roll : " + roll + " + " + (modRoll-roll-distanceModifier) + " vs " + AGILITY_TABLE[normalizeAgility(player.AG()) - 1] + " + " + (-distanceModifier) + " <" );
			if( roll == 1 || (roll != 6 && modRoll < AGILITY_TABLE[normalizeAgility(player.AG()) - 1]) )
				isAccurate = false;
			else
				isAccurate = true;
			
			if( !isAccurate && playerHasReroll )
			{
				LogWindow.instance().Log("> Player uses the PASS skill for a reroll! <");
				playerHasReroll = false;
			}
			else
				rollAgain = false;
		}
		
		ThrowBallResult result = new ThrowBallResult( ballPosition, isAccurate, player );

		// Resolve throw result	
		if(!isAccurate)
		{
			// the pass failed, the ball is scattered
			Position lastValidPosition = ballPosition;
			
			int nbScatters = 3;
			while( nbScatters > 0 && Pitch.isInsidePitch(ballPosition))
			{
				lastValidPosition = ballPosition;
				ballPosition = scatterBall(lastValidPosition);
				nbScatters--;
			}
						
			if( Pitch.isInsidePitch(ballPosition) )
			{
				LogWindow.instance().Log(player.name() + "'s " + message + " to " + target.toString() + " sucks and scatters to " + ballPosition.toString() );
				result.BallPosition = ballPosition;
			}
			else
			{
				LogWindow.instance().Log(player.name() + "'s " + message + " to " + target.toString() + " sucks and goes out the pitch!" );
				result.BallPosition = lastValidPosition;
				result.IsOutsidePitch = true;
			}			
		}
		else
		{
			LogWindow.instance().Log(player.name() + " throws the ball to " + target.toString() + " accurately!" );
		}
		
		return result;
	}
	
	/**
	 * The ball has landed on a location occupied by a player, so if possible, the player must attempt to catch the ball.
	 * @param player - the catcher
	 * @param isAccurate - specifies if the pass was accurate or not
	 * @return Returns true if the ball is caught, false otherwise.
	 */
	public static CatchBallResult catchBall(Player player, boolean isAccurate)
	{
		CatchBallResult result = new CatchBallResult(player, true);

		assert( player.state() == PlayerState.IN_PLAY );
		
		boolean playerHasReroll = player.hasSkill(Skill.CATCH);
		
		boolean rollAgain = true;
		boolean ballCaught = false;
		while( rollAgain )
		{
			int roll = Dice.rollDice(6);
			int modRoll = roll + GameEngine.getInstance().countEnemyTackleZones(player) * MODIFIER_ENEMY_TACKLE;
			// Correct for accurate passes
			modRoll += isAccurate ? CATCH_MODIFIER_ACCURATE_PASS : CATCH_MODIFIER_SCATTERED_BALL;
			
			// Roll is an automatic fail if 1,
			//            automatic success if 6
			// Modified roll counts distance, enemy players around, and tests against the agility table
			LogWindow.instance().Log("> Catch roll : " + roll + " + " + (modRoll-roll) + " vs " + AGILITY_TABLE[normalizeAgility(player.AG()) - 1] + " <" );
			if( roll == 1 || (roll != 6 && modRoll < AGILITY_TABLE[normalizeAgility(player.AG()) - 1]) )
				ballCaught = false;
			else
				ballCaught = true;
			
			if( !ballCaught && playerHasReroll )
			{
				LogWindow.instance().Log("> Player uses the CATCH skill for a re-roll! <");
				playerHasReroll = false;
			}
			else
				rollAgain = false;
		}
		
		if( ballCaught )
		{
			player.setHasBall(true);
			LogWindow.instance().Log(player.name() + " has caught the ball!");
		}
		else
		{
			result.Success = false;
			LogWindow.instance().Log(player.name() + " has failed to catch the ball!");
		}
		
		return result;
	}
	
	/**
	 * The player attempts to pick up the ball.
	 * @param player - the selected player
	 * @return Returns true if the player picks up the ball, false otherwise.
	 */
	public static boolean pickupBall(Player player)
	{
		boolean playerHasReroll = player.hasSkill(Skill.SURE_HANDS);
		
		boolean rollAgain = true;
		boolean ballPickedUp = false;
		while( rollAgain )
		{
			int roll = Dice.rollDice(6);
			int modRoll = roll + PICKUP_MODIFIER + GameEngine.getInstance().countEnemyTackleZones(player) * MODIFIER_ENEMY_TACKLE;
			
			// Roll is an automatic fail if 1,
			//            automatic success if 6
			// Modified roll counts distance, enemy players around, and tests against the agility table
			LogWindow.instance().Log("> Pickup roll : " + roll + " + " + (modRoll-roll) + " vs " + AGILITY_TABLE[normalizeAgility(player.AG()) - 1] + " <" );
			if( roll == 1 || (roll != 6 && modRoll < AGILITY_TABLE[normalizeAgility(player.AG()) - 1]) )
				ballPickedUp = false;
			else
				ballPickedUp = true;
			
			if( !ballPickedUp && playerHasReroll )
			{
				playerHasReroll = false;
				LogWindow.instance().Log("> Player uses the SURE HANDS skill for a reroll! <");
			}
			else
				rollAgain = false;
		}
		
		if( ballPickedUp )
		{
			LogWindow.instance().Log(player.name() + " has picked up the ball!");
			player.setHasBall(true);
		}
		else
		{
			LogWindow.instance().Log(player.name() + " failed to pick up the ball!");
		}
		
		return ballPickedUp;
	}
	
	/**
	 * Applies the scatter template.
	 * @param pos - the position of the ball drop (center of the template)
	 * @return Returns the scattered position.
	 */
	public static Position scatterBall(Position pos) 
	{
		Position scatter = scatter();
		return new Position( pos.x() + scatter.x(), pos.y() + scatter.y() );
	}
	
	/**
	 * Applies the scatter template for kickoff.
	 * @param pos - the position of the ball drop (center of the template)
	 * @return Returns the scattered position.
	 */	
	public static Position scatterKick(Position pos)
	{
		int roll = Dice.rollDice(6);
		Position scatter = scatter();
		return new Position( pos.x() + roll * scatter.x(), pos.y() + roll * scatter.y() );
	}
	
	/**
	 * Performs a scatter roll
	 * @return Returns the "relative" scatter position
	 */
	private static Position scatter()
	{
		int roll = Dice.rollDice(8);
		int x = 0, y = 0;
		switch(roll)
		{
		case 1: x = -1; y = -1; break;
		case 2: x =  0; y = -1; break;
		case 3: x =  1; y = -1; break;
		case 4: x = -1; y =  0; break;
		case 5: x =  1; y =  0; break;
		case 6: x = -1; y =  1; break;
		case 7: x =  0; y =  1; break;
		case 8: x =  1; y =  1; break;
		}
		
		return new Position(x, y);
	}
	
	/**
	 * Finds the last position inside the pitch between the origin and the current position
	 * @param origin Origin of the action
	 * @param current End of the action
	 * @return Returns the last valid position (in the pitch).
	 */
	public static Position findLastValidPosition( Position origin, Position current )
	{
		assert( Pitch.isInsidePitch( origin ) && !Pitch.isInsidePitch( current ) );
		
		// First, find last valid position between the origin and current position
		int dx = current.x() - origin.x();
		if( dx > 0 )
			dx = 1;
		else if( dx < 0 )
			dx = -1;
		
		int dy = current.y() - origin.y();
		if( dy < 0 )
			dy = -1;
		else if( dy > 0 )
			dy = 1;		
		
		Position last = new Position(origin);
		
		// Early exit
		if( dx == 0 && dy == 0 )
		{
			assert( Pitch.isInsidePitch(last) );
			return last;
		}
		
		Position next = new Position( last.x() + dx, last.y() + dy);
		
		while( Pitch.isInsidePitch(next) )
		{
			last = next;
			next = new Position( last.x() + dx, last.y() + dy);
		}
		
		return last;
	}
	
	/**
	 * Computes the throw-in target from a position on a sideline.
	 * @param origin The last valid position on the sideline
	 * @return The new throw-in position (could be outside the pitch)
	 */
	public static Position throwin( Position origin )
	{		
		Position template[] = new Position[3];
		
		if( origin.x() == 0 )
		{
			template[0] = new Position(1, -1);
			template[1] = new Position(1,  0);
			template[2] = new Position(1,  1);
		}
		else if( origin.x() == 25 )
		{
			template[0] = new Position(-1, -1);
			template[1] = new Position(-1,  0);
			template[2] = new Position(-1,  1);
		}
		else if( origin.y() == 0 )
		{
			template[0] = new Position(-1, 1);
			template[1] = new Position( 0, 1);
			template[2] = new Position( 1, 1);
		}
		else if( origin.y() == 14 )
		{
			template[0] = new Position(-1, -1);
			template[1] = new Position( 0, -1);
			template[2] = new Position( 1, -1);
		}
		else
		{
			assert( false );
		}
		
		// 2D6 squares counting the first one
		int direction = Dice.rollDice(3) - 1;
		int dist = Dice.rollDice(6, 2) - 1;
		
		return new Position( origin.x() + dist * template[direction].x(), origin.y() + dist * template[direction].y() );
	}

	/**
	 * This function check if the player can succeed from tackleZone.
	 * @param player 
	 * @return boolean false - if player can escape the tackleZone
	 */
	public static boolean isTackled(Player player)
	{
		if (GameEngine.getInstance().countEnemyTackleZones(player)==0)
			return false;
		
		boolean playerHasReroll = player.hasSkill(Skill.DODGE) && !player.hasUsedDodge();
		
		boolean rollAgain = true;
		boolean dodged = false;
		while( rollAgain )
		{
			int roll = Dice.rollDice(6);
			int modRoll = roll + DODGE_MODIFIER + GameEngine.getInstance().countEnemyTackleZones(player) * MODIFIER_ENEMY_TACKLE;
			
			// Roll is an automatic fail if 1,
			//            automatic success if 6
			// Modified roll counts distance, enemy players around, and tests against the agility table
			LogWindow.instance().Log("> Dodge roll : " + roll + " + " + (modRoll-roll) + " vs " + AGILITY_TABLE[normalizeAgility(player.AG()) - 1] + " <" );
			if( roll == 1 || (roll != 6 && modRoll < AGILITY_TABLE[normalizeAgility(player.AG()) - 1]) )
				dodged = false;
			else
				dodged = true;
			
			if( !dodged && playerHasReroll )
			{
				LogWindow.instance().Log("> Player uses the DODGE skill for a reroll! <");
				playerHasReroll = false;
				player.hasUsedDodge(true);
			}
			else
			{
				rollAgain = false;
			}
		}
		
		if( dodged )
			LogWindow.instance().Log("Player escaped from being knocked out!" );
		else
			LogWindow.instance().Log("Player is knocked out!" );
		
		return !dodged;		
	}
	
	/**
	 * Normalizes return value
	 * @param agility agility value
	 * @return Normalized value between 1 and 6
	 */
	private static int normalizeAgility(int agility)
	{
		if(agility <= 0)
			agility = 1;
		if(agility > 6)
			agility = 6;
		
		return agility;
	}
}
